package org.moonlight.jvm.instructions.references.invoke;

import org.moonlight.jvm.instructions.base.MethodInvokeLogic;
import org.moonlight.jvm.rtda.exclusive.Frame;
import org.moonlight.jvm.rtda.exclusive.OperandStack;
import org.moonlight.jvm.rtda.share.Object;
import org.moonlight.jvm.rtda.share.heap.Class;
import org.moonlight.jvm.rtda.share.heap.classmember.Method;
import org.moonlight.jvm.rtda.share.heap.symref.MethodRef;
import org.moonlight.jvm.rtda.share.methodarea.RtConstantPool;
import org.moonlight.jvm.rtda.share.methodarea.StringPool;
import org.moonlight.jvm.utils.MethodLookupUtil;

import java.util.Objects;

/**
 * invoke_virtual: 用于调用对象的实例方法，根据对象的实际类型进行分派
 *
 * @author Moonlight
 * @createTime 2023/8/30 16:49
 **/
public class InvokeVirtual extends MethodInvokeLogic {

    @Override
    public void execute(Frame frame) {
        // 当前类对象
        Class currentClass = frame.getMethod().getClazz();
        // 当前运行时常量池
        RtConstantPool rtCp = currentClass.getRtCp();
        // 方法符号引用
        MethodRef methodRef = (MethodRef) rtCp.getConstant(this.idx);
        // 解析出来的方法
        Method resolvedMethod = methodRef.resolvedMethod();
        // 这里解析出来的方法必须是非静态的方法
        if (resolvedMethod.isStatic()) {
            throw new IncompatibleClassChangeError();
        }
        // 从操作数栈中获取 this 引用
        Object ref = frame.getOperandStack().GetRefFromTop(resolvedMethod.getArgSlotCount() - 1);
        if (ref == null) {
            if (Objects.equals(methodRef.getName(), "println")) {
                innerPrintln(frame.getOperandStack(), methodRef.getDescriptor());
                return;
            }
            throw new NullPointerException();
        }
        // protected 方法
        if (resolvedMethod.isProtected()
            && resolvedMethod.getClazz().isSuperClassOf(currentClass)
            && !Objects.equals(resolvedMethod.getClazz().getPackageName(), currentClass.getPackageName())
            && ref.getClazz() != currentClass
            && !ref.getClazz().isSubClassOf(currentClass)) {
            throw new IllegalAccessError();
        }

        Method methodToBeInvoked = MethodLookupUtil.lookupMethodInClass(ref.getClazz(), methodRef.getName(), methodRef.getDescriptor());
        if (methodToBeInvoked == null || methodToBeInvoked.isAbstract()) {
            throw new AbstractMethodError();
        }

        invokeMethod(frame, methodToBeInvoked);
    }

    private void innerPrintln(OperandStack operandStack, String methodDesc) {
        switch (methodDesc) {
            case "(Z)V":
                System.out.println((operandStack.popInt() != 0) + "\n");
                break;
            case "(C)V":
                System.out.println(((char)operandStack.popInt()) + "\n");
                break;
            case "(I)V":
            case "(B)V":
            case "(S)V":
                System.out.println(operandStack.popInt() + "\n");
                break;
            case "(F)V":
                System.out.println(operandStack.popFloat() + "\n");
                break;
            case "(J)V":
                System.out.println(operandStack.popLong() + "\n");
                break;
            case "(D)V":
                System.out.println(operandStack.popDouble() + "\n");
                break;
            case "(Ljava/lang/String;)V":
                Object myString = operandStack.popRef();
                String string = StringPool.javaString(myString);
                System.out.println(string);
                break;
            default:
                throw new RuntimeException("println: " + methodDesc);
        }
        operandStack.popRef();
    }

}